package auto;

import java.io.BufferedReader;
import java.io.FileReader;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JLabel;
import util.SecurityManager;
import util.Sniffer;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.security.PublicKey;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.TreeMap;
import javax.crypto.SecretKey;
import rekeningrijdenserver.RekeningRijdenServer;
import tijdserver.TijdServer;
import util.Certificate;
import util.Message;
import util.Message.MessageType;
import util.NetwerkComponent;

/**
 *
 * @author Jonas
 */
public class BoordComputerImpl implements BoordComputer {
    // eigen identificatie (niet dat dit écht nodig is met maar 1 boordcomputer, maar meerder kunnen dus wel

    private static int IDCounter = 0;
    private String ID;
    // servers (+ GPS en sniffer en locatieLuisteraar) waarmee gecommuniceerd zal moeten worden
    private TijdServer tijdServer;
    private RekeningRijdenServer rekeningRijdenServer;
    private GPS attachedGPS;
    private Sniffer sniffy;
    JLabel locationListener;
    // data voor verwerking
    private TreeMap<Long, Location> locations = new TreeMap<Long, Location>();    // locaties die nog niet verwerkt zijn
    HashMap<String, Object> fieldsToSend = new HashMap<String, Object>();         // velden die verstuurd worden naar de overheid
    ArrayList<HashMap<String, Object>> savedField = new ArrayList<HashMap<String, Object>>();// opgeslagenn data

    // sleutel voor comm
    SecretKey secret;

    public BoordComputerImpl(TijdServer tijd, RekeningRijdenServer rekening) {
        tijdServer = tijd;
        rekeningRijdenServer = rekening;
        ID = "BC-id" + IDCounter;
        IDCounter++;
        attachedGPS = null;
        sniffy = null;
    }

    public void pushData(Location location, long timestamp) {
        // 1. encrypt the raw data
        String encrypted = "";
        try {
            encrypted = SecurityManager.encryptRawGPSData(timestamp, location, "./bestuurderSecret.key");
        } catch (Exception e) {
        }
        try {
            PrintWriter out = new PrintWriter(new FileWriter("gpsData.txt", true));
            out.print(encrypted);
            out.print("\n");
            out.close();
        } catch (IOException ex) {
        }


        // 2. store the raw
        synchronized (locations) {
            locations.put(timestamp, location);
        }

        // OPTIONAL: System.out's to show the raw data + the encrypted data + the possible decrypted entries
        if (attachedGPS.isMoving()) {
            locationListener.setText("Wagen rijdt: locatie = " + location);
        } else {
            locationListener.setText("Wagen staat stil: laatste locatie = " + location); // push bij het stoppen
        }
        // na een zekere tijd: stuur huidige verzamelde data door
        if (locations.size() == 10) {
            int[] categoryData = new int[]{0, 0, 0};
            long begin;
            long end;
            synchronized (locations) {
                begin = locations.firstKey();
                end = locations.lastKey();
                for (Location loc : locations.values()) {
                    if (loc.getCategory() == Location.Category.DUUR) {
                        categoryData[0]++;
                    }
                    if (loc.getCategory() == Location.Category.STANDAARD) {
                        categoryData[1]++;
                    }
                    if (loc.getCategory() == Location.Category.GOEDKOOP) {
                        categoryData[2]++;
                    }
                }
                locations.clear();
            }

            // 1e sendMessage die handelingen "triggert"
            // => verdere berichten kunnen in sendMessage verzonden worden [a la: if(type=="wat ik als 1e antwoord verwacht"){stuurTweedeBericht()} ]

            fieldsToSend = new HashMap<String, Object>();
            ArrayList<Object> toSign = new ArrayList<Object>();
            fieldsToSend.put("TBegin", begin);
            toSign.add(begin);
            fieldsToSend.put("TEnd", end);
            toSign.add(end);
            fieldsToSend.put("FCat", categoryData);
            toSign.add(categoryData);

            byte[] signature = SecurityManager.signData(toSign, "./bestuurderPrivate.key");
            fieldsToSend.put("Signature", signature);

            HashMap<String, Object> hash = new HashMap<String, Object>();
            hash.put("Signature", signature);
            Message transmit = new Message(Message.MessageType.TS_REQ, hash);
            tijdServer.sendMessage(transmit, this);
        }
    }

    public void attachGPS(GPS gps) {
        if (attachedGPS == null) {
            attachedGPS = gps;
            System.out.println("GPS " + gps + " attached");
            attachedGPS.subscribe(this);
        } else {
            System.out.println("GPS " + attachedGPS + " replaced by " + gps);
            attachedGPS = gps;
        }
    }

    public void detachGPS() {
        String occupied = (attachedGPS == null) ? "" : " anymore";
        attachedGPS.unsubscribe();
        attachedGPS = null;
        System.out.println("No gps attached" + occupied);
    }

    public boolean startEngine() {
        if (attachedGPS == null) {
            System.out.println("No GPS present");
            return false;
        } else {
            attachedGPS.startMoving();
            System.out.println("Engine started");
            return true;
        }

    }

    public void stopEngine() {
        attachedGPS.stopMoving();
        System.out.println("Engine stopped");
    }

    public void sendMessage(Message msg, NetwerkComponent sender) {
        if (sniffy != null) {
            sniffy.readTransmission(msg, sender, this);
        }
        if (msg.getMessageType() == MessageType.TS_ANSWER) { // volgende: begin connectie met Server
            byte[] data = (byte[]) fieldsToSend.get("Signature");
            Long time = (Long) msg.getData().get("Timestamp");
            byte[] signature = (byte[]) msg.getData().get("SignTS");

            ArrayList<Object> signed = new ArrayList<Object>();
            signed.add(data);
            signed.add(time);

            boolean ok = SecurityManager.verifyData(signed, "./TSPublic.key", signature);
            if (ok) {
                fieldsToSend.put("Time", time);
                fieldsToSend.put("SignTS", signature);
                
                // begin handshaking
                HashMap<String, Object> clientHello = new HashMap<String, Object>();
                clientHello.put("client_nonce", SecurityManager.generateNonce());
                clientHello.put("RSA", "2048");
                clientHello.put("SHA-256", "");
                clientHello.put("AES", "128");
                rekeningRijdenServer.sendMessage(new Message(MessageType.CLIENT_HELLO, clientHello), this);
            }
        }
        if (msg.getMessageType() == MessageType.SERVER_HELLO) {
            if (msg.getData().get("response").equals("ok")) {
                // ok, wait for certificate
            } else {
                System.out.println("Error @ SERVER_HELLO");
            }
        }
        if (msg.getMessageType() == MessageType.SERVER_CERT) {
            //obtain server's public key
            Certificate certificate = (Certificate) msg.getData().get("certificate");
            PublicKey serverPublicKey = certificate.getPublicKey();

            // send certificate
            HashMap<String, Object> cert = new HashMap<String, Object>();
            try {
                cert.put("certificate", SecurityManager.generateCertificate("./bestuurderPublic.key"));
            } catch (IOException ex) {
            }
            rekeningRijdenServer.sendMessage(new Message(MessageType.CLIENT_CERT, cert), this);

            // clientKeyExchange
            byte[] salt = SecurityManager.generateSalt();
            char[] pms = SecurityManager.generatePreMasterSecret();
            secret = SecurityManager.getSecretKey(pms, salt);
            HashMap<String, Object> keyExchange = new HashMap<String, Object>();
            keyExchange.put("salt", salt);
            keyExchange.put("pre_master_secret", pms);
            Message m = null;
            try {
                m = SecurityManager.encryptMessage(serverPublicKey, new Message(MessageType.CLIENT_KEY_EXCHANGE, keyExchange));
            } catch (Exception e){
                System.err.println(e);
            } 
            rekeningRijdenServer.sendMessage(m, this);
        }
        if (msg.getMessageType() == MessageType.CONN_OK) {
                    Message enc;
                    try {
                        Message toEnc = new Message(Message.MessageType.RRS_REQ, fieldsToSend);
                        enc = SecurityManager.encryptMessage(secret, toEnc);
                        rekeningRijdenServer.sendMessage(enc, this);
                    } catch (Exception ex) {
                        Logger.getLogger(BoordComputerImpl.class.getName()).log(Level.SEVERE, null, ex);
                    }
        }
        if (msg.getMessageType() == MessageType.RRS_ANSWER) {
            try {
                fieldsToSend.put("SignatureOverheid", msg.getData().get("Signature"));
                fieldsToSend.put("TimestampOverheid", msg.getData().get("Time"));
                fieldsToSend.put("SignTS2", msg.getData().get("SignTS2"));
                savedField.add(new HashMap<String, Object>(fieldsToSend));
            } catch (Exception ex) {
                Logger.getLogger(BoordComputerImpl.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
        if (msg.getMessageType() == MessageType.ENCRYPTED) {
            try {
                Message decrypted = SecurityManager.decryptMessage(secret, msg);
                sendMessage(decrypted, this);
            } catch (Exception ex) {
            }
        }
        if (msg.getMessageType() == MessageType.PRE_ENCRYPTED) {
            try {
                Message decrypted = SecurityManager.decryptMessage(SecurityManager.readPrivateKeyFromFile("./bestuurderPrivate.key"), msg);
                sendMessage(decrypted, this);
            } catch (Exception ex) {
            }
        }
    }

    public void addSniffer(Sniffer sniffer) {
        sniffy = sniffer;
        System.out.println("Sniffer added");
    }

    public void removeSniffer() {
        sniffy = null;
        System.out.println("Sniffer removed");
    }

    @Override
    public String toString() {
        return ID;
    }

    @Override
    public RekeningRijdenServer getServer() {
        return rekeningRijdenServer;
    }

    public void setLocationListener(JLabel systemStatus) {
        locationListener = systemStatus;
    }

    public boolean checkWithServer(RekeningRijdenServer rrs) {
        try{
            int[] serverCats = rrs.getCategoriesPhysical();
            int[] autoCats = new int[]{0,0,0};

            HashMap<Long,Location> returnHash;

            BufferedReader in = new BufferedReader(new FileReader("gpsData.txt"));
            String line = "";
            String encrypted = "";
            while((line=in.readLine())!=null){
                if(line.length()==0)
                    continue;
                encrypted = encrypted.concat(line);
                if(line.charAt(line.length()-1) == '='){
                    returnHash = SecurityManager.decryptRawGPSData(encrypted, "./bestuurderSecret.key");
                    for(Long timestamp : returnHash.keySet()){
                        Location.Category cat = returnHash.get(timestamp).getCategory();
                        if(cat == Location.Category.DUUR)
                            autoCats[0]++;
                        if(cat == Location.Category.STANDAARD)
                            autoCats[1]++;
                        if(cat == Location.Category.GOEDKOOP)
                            autoCats[2]++;
                    }
                    encrypted = "";
                }
            }
            for(int i=0;i<3;i++)
                if(serverCats[i]>autoCats[i])
                    return false;
            return true;
        }catch(Exception e){return false;}
    }
}
